package com.objecteffects.template.tumblr;

import java.io.IOException;

import javax.inject.Singleton;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import com.objecteffects.template.oauth.Tumblr;

import org.scribe.builder.ServiceBuilder;
import org.scribe.builder.api.TumblrApi;
import org.scribe.model.OAuthRequest;
import org.scribe.model.Response;
import org.scribe.model.Token;
import org.scribe.model.Verb;
import org.scribe.oauth.OAuthService;

import org.apache.commons.lang3.exception.ExceptionUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Rusty Wright
 */
@Singleton
public class TumblrResponse<T> {
    /**
     */
    private static final int RESPONSE_OK = 200;

    private final transient Logger log = LoggerFactory
        .getLogger(TumblrResponse.class);

    private final String blog;
    private final String method;
    private final Token accessToken;
    private final Class<? extends T> type;
    private final Integer offset;
    private final Long id;
    private final String reblogKey;
    private final Boolean user;
    private final Integer limit;
    private final Boolean queue;
    private final Boolean drafts;

    public static class Builder<T> {
        String blog;
        String method;
        Token accessToken;
        Class<? extends T> type;
        Integer offset;
        Long id;
        String reblogKey;
        Boolean user = Boolean.FALSE;
        Integer limit;
        Boolean queue = Boolean.FALSE;
        Boolean drafts = Boolean.FALSE;

        public Builder<T> blog(final String _blog) {
            this.blog = _blog;

            return this;
        }

        public Builder<T> method(final String _method) {
            this.method = _method;

            return this;
        }

        public Builder<T> accessToken(final Token _accessToken) {
            this.accessToken = _accessToken;

            return this;
        }

        public Builder<T> type(final Class<? extends T> _type) {
            this.type = _type;

            return this;
        }

        public Builder<T> offset(final Integer _offset) {
            this.offset = _offset;

            return this;
        }

        public Builder<T> id(final Long _id) {
            this.id = _id;

            return this;
        }

        public Builder<T> reblogKey(final String _reblogKey) {
            this.reblogKey = _reblogKey;

            return this;
        }

        public Builder<T> setUser() {
            this.user = Boolean.TRUE;

            return this;
        }

        public Builder<T> limit(final Integer _limit) {
            this.limit = _limit;

            return this;
        }

        public Builder<T> isQueue() {
            this.queue = Boolean.TRUE;

            return this;
        }

        public Builder<T> isDrafts() {
            this.drafts = Boolean.TRUE;

            return this;
        }

        public TumblrResponse<T> build() {
            return new TumblrResponse<>(this);
        }
    }

    TumblrResponse(final Builder<T> builder) {
        if (!builder.user.booleanValue() && builder.blog == null) {
            throw new IllegalStateException("builder blog is not set");
        }

        if (builder.method == null) {
            throw new IllegalStateException("builder method is not set");
        }

        if (builder.accessToken == null) {
            throw new IllegalStateException("builder accessToken is not set");
        }

        if (builder.type == null) {
            throw new IllegalStateException("builder type is not set");
        }

        if (builder.user.booleanValue() &&
            (builder.queue.booleanValue() || builder.drafts.booleanValue())) {
            throw new IllegalStateException(
                "can't have user and queue or drafts set together");
        }

        this.blog = builder.blog;
        this.method = builder.method;
        this.accessToken = builder.accessToken;
        this.type = builder.type;

        this.offset = builder.offset;
        this.id = builder.id;
        this.reblogKey = builder.reblogKey;
        this.user = builder.user;
        this.limit = builder.limit;
        this.queue = builder.queue;
        this.drafts = builder.drafts;
    }

    public T responseMapper() throws IOException {
        final String methodUrl = this.methodUrl();

        this.log.debug("methodUrl: {}", methodUrl);

        final Response response = this.getResponse(methodUrl);

        final String body = response.getBody();

        this.log.debug("response ({}): {}", this.type.getName(), body);

        final ObjectMapper mapper = new ObjectMapper();

        // final Object obj = mapper.readValue(body, Object.class);
        // final String json =
        // mapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
        // this.log.debug("json response: {}", json);

        if (!this.validResponse(mapper, body)) {
            return null;
        }

        final T mappedResponse = mapper.readValue(body, this.type);

        this.log.debug("mappedResponse: {}", mappedResponse);

        return mappedResponse;
    }

    private String methodUrl() {
        final StringBuffer methodUrl = new StringBuffer(Tumblr.API_URL);

        if (!this.user.booleanValue()) {
            methodUrl.append("blog/");
            methodUrl.append(this.blog);

            if (!this.blog.contains(".")) {
                methodUrl.append(".tumblr.com");
            }

            methodUrl.append("/");
        }

        if (this.queue.booleanValue()) {
            methodUrl.append("posts/queue");
        }
        else if (this.drafts.booleanValue()) {
            methodUrl.append("posts/drafts");
        }
        else {
            methodUrl.append(this.method);
        }

        return methodUrl.toString();
    }

    /**
     * @param methodUrl
     * @param service
     * @param _accessToken
     * @throws IOException
     * @throws JsonParseException
     * @throws JsonMappingException
     */
    private Response getResponse(final String methodUrl) {
        final OAuthService service =
            new ServiceBuilder().provider(TumblrApi.class)
            .apiKey(Tumblr.CONSUMER_KEY).apiSecret(Tumblr.CONSUMER_SECRET)
            .build();

        // Step 5
        final OAuthRequest request = new OAuthRequest(Verb.GET, methodUrl);

        request.addQuerystringParameter("api_key", Tumblr.CONSUMER_KEY);

        if (this.offset != null) {
            request.addQuerystringParameter("offset", Integer
                                            .toString(this.offset.intValue()));
        }

        if (this.id != null) {
            request.addQuerystringParameter("id",
                                            Long.toString(this.id.longValue()));
        }

        if (this.reblogKey != null) {
            request.addQuerystringParameter("reblog_key", this.reblogKey);
        }

        if (this.limit != null) {
            request.addQuerystringParameter("limit", Integer
                                            .toString(this.limit.intValue()));
        }

        this.log.debug("request complete url: {}", request.getCompleteUrl());

        this.log.debug("oauth params: {}", request.getOauthParameters());

        // the access token from step 4
        service.signRequest(this.accessToken, request);

        final Response response = request.send();

        return response;
    }

    private boolean validResponse(final ObjectMapper mapper, final String body)
        throws IOException {
        final TumblrMetaResponse meta;

        try {
            meta = mapper.readValue(body, TumblrMetaResponse.class);

            this.log.debug("meta: {}", meta);
        }
        catch (JsonParseException | JsonMappingException ex) {
            this.log.warn("mangled response: {}",
                          ExceptionUtils.getStackTrace(ex));

            return false;
        }

        if (meta.getMeta().getStatus() != TumblrResponse.RESPONSE_OK) {
            this.log.warn("bad status: {}",
                          Integer.valueOf(meta.getMeta().getStatus()));

            return false;
        }

        this.log.debug("is a valid response");

        return true;
    }
}
